package a02;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import util.Pair;

public class Othello extends AbstractRegularGame<OthelloMove> implements
	Serializable {

    private static final long serialVersionUID = 1L;
    private int passed;

    Othello() {
	super((byte) 8, (byte) 8);
	this.getB()[3][3] = this.getPlayerOne();
	this.getB()[4][4] = this.getPlayerOne();
	this.getB()[3][4] = this.getPlayerTwo();
	this.getB()[4][3] = this.getPlayerTwo();
	this.setMovesDone(4);
    }

    @Override
    public Othello doMove(OthelloMove om) {
	final Pair<Byte, Byte> m = om.getMv();
	final Othello result = (Othello) this.clone();
	result.setPlayer(this.nextPlayer());
	if (om instanceof NoMove) {
	    return result;
	}

	result.getB()[m.fst][m.snd] = this.getPlayer();
	for (Pair<Byte, Byte> t : om.getTurn()) {
	    result.getB()[t.fst][t.snd] = this.getPlayer();
	}

	result.setMovesDone(this.getMovesDone() + 1);
	return result;
    }

    public Othello doMove(Pair<Byte, Byte> m) {
	if (this.getB()[m.fst][m.snd] == getNone()) {
	    final List<Pair<Byte, Byte>> turn = this.toTurn(m);
	    if (!turn.isEmpty()) {
		return this.doMove(new OthelloMove(m, turn));
	    }
	}
	return this;
    }

    @Override
    public boolean ended() {
	return this.noMoreMove();
    }

    public boolean isPassed() {
	return this.passed == 1;
    }

    public void setGameDone() {
	this.passed = 2;
    }

    public boolean pass(OthelloControl control) {
	System.out.println("Pass in Othello");
	boolean answer = true;
	Othello result = this.clone();
	result.setPlayer(this.nextPlayer());
	control.getBoard().setGame(result);
	control.setSession(result);
	if (isPassed()) {
	    result.setGameDone();
	    answer = false;
	} else {
	    result.setPassed(true);
	}
	control.refresh();
	return answer;
    }

    public void setPassed(boolean passed) {
	this.passed = passed ? 1 : 0;
    }
    
    @Override
    public Othello clone() {
	return (Othello) super.clone();
    }

    @Override
    public int evalState(byte player) {
	int result = 0;
	for (byte trs = 0; trs < this.getColumns(); trs++) {
	    for (byte pos = 0; pos < this.getRows(); pos++) {
		if (this.getB()[trs][pos] == player) {
		    result++;
		}
	    }
	}	
	return result;
    }

    @Override
    public List<OthelloMove> moves() {
	final List<OthelloMove> result = new LinkedList<OthelloMove>();
	for (byte trs = 0; trs < this.getColumns(); trs++) {
	    for (byte pos = 0; pos < this.getRows(); pos++) {
		final Pair<Byte, Byte> m = new Pair<Byte, Byte>(trs, pos);
		if (this.getB()[trs][pos] == getNone()) {
		    final List<Pair<Byte, Byte>> turn = this.toTurn(m);
		    if (!turn.isEmpty()) {
			result.add(new OthelloMove(m, turn));
		    }
		}
	    }
	}
	if (result.isEmpty()) {
	    result.add(new NoMove());
	}
	return result;
    }

    @Override
    public boolean noMoreMove() {
	return this.getMovesDone() == 64 || passed == 2 || noMoreEnemies();
    }

    private boolean noMoreEnemies() {
	return this.evalState(getOne())==0 || this.evalState(getTwo())==0;
    }

    @Override
    public Othello setAtPosition(byte column, byte row) {
	return this.doMove(new Pair<Byte, Byte>(column, row));
    }

    @Override
    public boolean wins(byte player) {
	return this.noMoreMove()
		&& this.evalState(player) > this.evalState(this
			.otherPlayer(player));
    }

    List<Pair<Byte, Byte>> toTurn(Pair<Byte, Byte> m) {
	final List<Pair<Byte, Byte>> result = new LinkedList<Pair<Byte, Byte>>();

	if (m.fst + 2 < this.getColumns()) {
	    if (this.getB()[m.fst + 1][m.snd] == this.nextPlayer()) {
		byte i = (byte) (m.fst + 2);
		while (i < this.getColumns()
			&& this.getB()[i][m.snd] != getNone()
			&& this.getB()[i][m.snd] != this.getPlayer()) {
		    i++;
		}
		if (i < this.getColumns()
			&& this.getB()[i][m.snd] == this.getPlayer()) {
		    for (i = (byte) (i - 1); i > m.fst; i--) {
			result.add(new Pair<Byte, Byte>(i, m.snd));
		    }
		}
	    }
	}
	if (m.fst - 2 >= 0) {
	    if (this.getB()[m.fst - 1][m.snd] == this.nextPlayer()) {
		byte i = (byte) (m.fst - 2);
		while (i >= 0 && this.getB()[i][m.snd] != getNone()
			&& this.getB()[i][m.snd] != this.getPlayer()) {
		    i--;
		}
		if (i >= 0 && this.getB()[i][m.snd] == this.getPlayer()) {
		    for (i = (byte) (i + 1); i < m.fst; i++) {
			result.add(new Pair<Byte, Byte>(i, m.snd));
		    }
		}
	    }
	}
	if (m.snd + 2 < this.getRows()) {
	    if (this.getB()[m.fst][m.snd + 1] == this.nextPlayer()) {
		byte i = (byte) (m.snd + 2);
		while (i < this.getRows() && this.getB()[m.fst][i] != getNone()
			&& this.getB()[m.fst][i] != this.getPlayer()) {
		    i++;
		}
		if (i < this.getRows()
			&& this.getB()[m.fst][i] == this.getPlayer()) {
		    for (i = (byte) (i - 1); i > m.snd; i--) {
			result.add(new Pair<Byte, Byte>(m.fst, i));
		    }
		}
	    }
	}
	if (m.snd - 2 >= 0) {
	    if (this.getB()[m.fst][m.snd - 1] == this.nextPlayer()) {
		byte i = (byte) (m.snd - 2);
		while (i >= 0 && this.getB()[m.fst][i] != getNone()
			&& this.getB()[m.fst][i] != this.getPlayer()) {
		    i--;
		}
		if (i >= 0 && this.getB()[m.fst][i] == this.getPlayer()) {
		    for (i = (byte) (i + 1); i < m.snd; i++) {
			result.add(new Pair<Byte, Byte>(m.fst, i));
		    }
		}
	    }
	}

	if (m.fst + 2 < this.getColumns() && m.snd + 2 < this.getRows()) {
	    if (this.getB()[m.fst + 1][m.snd + 1] == this.nextPlayer()) {
		byte i = (byte) 2;
		while (m.fst + i < this.getColumns()
			&& m.snd + i < this.getRows()
			&& this.getB()[m.fst + i][m.snd + i] != getNone()
			&& this.getB()[m.fst + i][m.snd + i] != this
				.getPlayer()) {
		    i++;
		}

		if (m.fst + i < this.getColumns()
			&& m.snd + i < this.getRows()
			&& this.getB()[m.fst + i][m.snd + i] == this
				.getPlayer()) {
		    for (i = (byte) (i - 1); i > 0; i--) {
			result.add(new Pair<Byte, Byte>((byte) (m.fst + i),
				(byte) (m.snd + i)));
		    }
		}
	    }
	}

	if (m.fst - 2 >= 0 && m.snd - 2 >= 0) {
	    if (this.getB()[m.fst - 1][m.snd - 1] == this.nextPlayer()) {
		byte i = (byte) 2;
		while (m.fst - i >= 0
			&& m.snd - i >= 0
			&& this.getB()[m.fst - i][m.snd - i] != getNone()
			&& this.getB()[m.fst - i][m.snd - i] != this
				.getPlayer()) {
		    i++;
		}
		if (m.fst - i >= 0
			&& m.snd - i >= 0
			&& this.getB()[m.fst - i][m.snd - i] == this
				.getPlayer()) {
		    for (i = (byte) (i - 1); i > 0; i--) {
			result.add(new Pair<Byte, Byte>((byte) (m.fst - i),
				(byte) (m.snd - i)));
		    }
		}
	    }
	}

	if (m.fst + 2 < this.getColumns() && m.snd - 2 >= 0) {
	    if (this.getB()[m.fst + 1][m.snd - 1] == this.nextPlayer()) {
		byte i = (byte) 2;
		while (m.fst + i < this.getColumns()
			&& m.snd - i >= 0
			&& this.getB()[m.fst + i][m.snd - i] != getNone()
			&& this.getB()[m.fst + i][m.snd - i] != this
				.getPlayer()) {
		    i++;
		}

		if (m.fst + i < this.getColumns()
			&& m.snd - i >= 0
			&& this.getB()[m.fst + i][m.snd - i] == this
				.getPlayer()) {
		    for (i = (byte) (i - 1); i > 0; i--) {
			result.add(new Pair<Byte, Byte>((byte) (m.fst + i),
				(byte) (m.snd - i)));
		    }
		}
	    }
	}
	if (m.fst - 2 >= 0 && m.snd + 2 < this.getRows()) {
	    if (this.getB()[m.fst - 1][m.snd + 1] == this.nextPlayer()) {
		byte i = (byte) 2;
		while (m.fst - i >= 0
			&& m.snd + i < this.getRows()
			&& this.getB()[m.fst - i][m.snd + i] != getNone()
			&& this.getB()[m.fst - i][m.snd + i] != this
				.getPlayer()) {
		    i++;
		}
		if (m.fst - i >= 0
			&& m.snd + i < this.getRows()
			&& this.getB()[m.fst - i][m.snd + i] == this
				.getPlayer()) {
		    for (i = (byte) (i - 1); i > 0; i--) {
			result.add(new Pair<Byte, Byte>((byte) (m.fst - i),
				(byte) (m.snd + i)));
		    }
		}
	    }
	}
	return result;
    }
}
